/* Copyright Statement //<>//
 Hanoi Tower Play Station
 mouse drag the plate and drop between poles.
 
 
 this project needs sound library for button click sound effect etc.
 refer to "sound libray.png" to install it
 */

import processing.sound.*;
PFont fontText;
PFont fontClk;
PImage imgStop;

String copyRight = "Hanoi Play Station. Chuyi Li. Sep 17 2024";
String version = " Ver1.7";
// ======== sound files for button click  ======== //
SoundFile sndUp = null;
SoundFile sndDn = null;
SoundFile sndGo = null;
SoundFile sndPause = null;
SoundFile sndDone = null;
SoundFile sndDrop = null;
SoundFile sndLift = null;

void loadSound() {
  sndUp = new SoundFile(this, "up.mp3");
  sndDn = new SoundFile(this, "down.mp3");
  sndGo = new SoundFile(this, "go.mp3");
  sndPause = new SoundFile(this, "pause.mp3");
  sndDone = new SoundFile(this, "done.mp3");
  sndDrop = new SoundFile(this, "drop.mp3");
  sndLift = new SoundFile(this, "lift.mp3");

  btnUp.sound = sndUp;
  btnDn.sound = sndDn;
  btnGo.sound = sndGo;
  btnPause.sound = sndPause;
}

// ================= //

void playSound(SoundFile snd) {
  if (snd != null) {
    snd.play(); // if sound file is not loaded yet, don't play it.
  }
}

// ================= //

void setup() {
  thread("loadSound");

  // ==== {
  size(1800, 900);
  smooth();

  rectMode(RADIUS);
  frameRate(100);

  strokeWeight(2);
  // ====== }

  // ==== {
  fontText = loadFont("VinerHandITC-128.vlw");
  textFont(fontText);
  fontClk = loadFont("BlackadderITC-Regular-128.vlw");
  // ====== }

  // ==== {
  imgStop = loadImage("stop.png");
  btnStop.img = imgStop; // stop button is an image button
  // ====== }

  // ==== {
  thread("thread1sec"); // 1 sec thread for stopwatch
  thread("threadTurnOffDragRangeBox"); // blink drag range boxes some times at start up
  // ====== }

  preparePlates(numPlates); // initial power up plates

  println(copyRight + version); // last action in setup()
}

// ================ draw ================ //
int stateMachine = 0;

cplate movingPlate;

int dragPoleNo;
cpole dragPole;

int dropPoleNo;
cpole dropPole;

int targetY;
final int moveSteps = 80;

int blink;

void draw() {
  paintBackground(); // 1st action in draw()
  draw3Poles(); // 2nd action in draw()
  draw10plates();

  // ==== {
  switch(stateMachine) {
  case 0:
    { // idle state, nothing to do
      break;
    }
  case 1:
    {
      clearDragReleasePending();
      stateMachine++;
      break;
    }
  case 2:
    { // check if mouse drag, get the moving plate if drag on the pole
      dragPoleNo = handleMouseDrag();
      if (dragPoleNo >= 0) {
        // drag on pole active range found (0, 1, 2 for pole 1, 2, 3)
        dragPole = poles[dragPoleNo];
        movingPlate = dragPole.plateStack.pop();
        if (movingPlate != null) {
          println(movingPlate.plateNo + "  " + movingPlate.colrName);
          targetY = 200;
          clearDragReleasePending();
          playSound(sndLift);
          stateMachine = 3;
        } // else = dragging on an empty pole
      } // else {} -2 means drag, but not in any 3 poles. -1 means no drag
      break;
    }
  case 3:
    { // lift up the moving plate
      if (movingPlate.y - targetY > moveSteps) {
        movingPlate.y -= moveSteps;
      } else {
        movingPlate.y = targetY;
        stateMachine = 4;
      }
      clearDragReleasePending();
      break;
    }
  case 4:
    { // horizontal moving following mouseX
      movingPlate.x = mouseX; // plate follow mouse drag

      dropPoleNo = handlemouseRelease();
      if (dropPoleNo == -2 || dropPoleNo == dragPoleNo) {
        // release outside the range of 3 poles, or on the same pole drage out of
        // put it back
        dragPole.pushPlateIn(movingPlate);
        playSound(sndDrop);
        stateMachine = 1;
      } else if (dropPoleNo >= 0) {
        // release on pole active range found (0, 1, 2 for pole 1, 2, 3)
        dropPole = poles[dropPoleNo];
        if (movingPlate.plateNo > dropPole.getTopPlateNo()) { // no plate = -1, plate No = [0, 9]
          // moving plate is smaller than the plate in drop pole, or drop pole is empty
          // then drop it to the drop pole
          movingPlate.x = dropPole.x; // fine tune the X to pole
          targetY = PLATE_YPOS[dropPole.getPlatesCnt()];
          clearDragReleasePending();
          stateMachine = 5;
        } else {
          // moving plate is bigger then the plate in drop pole, reject, put it back
          dragPole.pushPlateIn(movingPlate);
          playSound(sndDrop);
          stateMachine = 1;
        }
      } // else {}   -1 means no release
      break;
    }
  case 5:
    { // drop down
      if (targetY - movingPlate.y > moveSteps) {
        movingPlate.y += moveSteps;
      } else {
        movingPlate.y = targetY;
        dropPole.pushPlateIn(movingPlate);
        playSound(sndDrop);

        // checkwin
        if (pole2.plateStack.size() == numPlates) { // won
          stopWatch.stop();

          allButtonEnable();
          btnStop.invisible();
          btnPause.invisible();

          playSound(sndDone);
          blink = 20;
          stateMachine = 6;
        } else {
          stateMachine = 1;
        }
      }
      break;
    }
  case 6:
    { // win blink
      if (blink > 0) {
        blink--;
        fill(#ED0E0E);
        textSize(200);
        text("Well Done", 320, 500);
      } else {
        blink = 20;
        stateMachine = 7;
      }
      break;
    }
  case 7:
    { // win blink
      if (blink > 0) {
        blink--;
      } else {
        blink = 20;
        stateMachine = 6;
      }
      break;
    }

    // ====== /
  default:
    {
      stateMachine = 0;
    }
  }
  // ====== }

  // ==== {
  stopWatch.paint();
  // ====== }

  drawMouseClickCircle(); // last action in draw()
}

void paintBackground() {
  background(#D4E9FA);

  // ==== { copyright message
  fill(#0BD3C4); // text color
  textSize(50);
  text(copyRight, 10, 60);
  // ====== }

  drawallBtns();

  drawNumPlates(); // information of number of plates only
}

// ======== mouse click  ======== //
int mcc_radius = 0;
int mcc_x, mcc_y;
void drawMouseClickCircle() {
  if (mcc_radius > 0) {
    noFill();
    stroke(#F50707);
    circle(mcc_x, mcc_y, 50 - mcc_radius);
    mcc_radius -= 3;
  }
}

void setMouseClickCirlce(int x, int y) {
  mcc_x = x;
  mcc_y = y;
  mcc_radius = 20;
}

void clearDragReleasePending() {
  mouseDraggedPending = false;
  mouseReleasePending = false;
}

void mouseClicked() {
  checkMouseClick(mouseX, mouseY);
  // println("clk: " + mouseX + " " + mouseY); // degug usage
}

// ======== mouse drag  ======== //
boolean mouseDraggedPending = false;
int mouseDragX;
int mouseDragY;
void mouseDragged() {
  if (!mouseDraggedPending) {
    mouseDraggedPending = true;
    mouseDragX = mouseX;
    mouseDragY = mouseY;
  }
}

// rerun 0, 1, 2 means drag on pole 0, 1, 2
// return -1 means no drag
// return -2 means drag, but not in any 3 poles
int handleMouseDrag() {
  int rslt = -1;
  if (mouseDraggedPending) {
    if (stopWatch.status == stopWatchStatus.RUNNING) {
      rslt = checkPolesRange(mouseDragX, mouseDragY);
    }
    mouseDraggedPending = false; // last action in handleMouseDrag
  }
  return rslt;
}

// ======== mouse release  ======== //
// Note: release includes click release and drag release
boolean mouseReleasePending = false;
int mouseReleaseX;
int mouseReleaseY;
void mouseReleased() {
  if (!mouseReleasePending) {
    mouseReleasePending = true;
    mouseReleaseX = mouseX;
    mouseReleaseY = mouseY;
  }
}

// rerun 0, 1, 2 means relase on pole 0, 1, 2
// return -1 means no release
// return -2 means release, but not in any 3 poles
int handlemouseRelease() {
  int rslt = -1;
  if (mouseReleasePending) {
    if (stopWatch.status == stopWatchStatus.RUNNING) {
      rslt = checkPolesRange(mouseReleaseX, mouseReleaseY);
    }
    mouseReleasePending = false; // last action in handlemouseRelease
  }
  return rslt;
}

// ================= //

void threadTurnOffDragRangeBox() {
  for (int i = 5; i > 0; i--) {
    for (cpole p : poles) {
      p.drawDragRange = true;
    }
    delay(500);
    for (cpole p : poles) {
      p.drawDragRange = false;
    }
    delay(500);
  }
}
